/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package agen.cruce;

import agen.seleccion.MetodoSeleccionCombinado;
import agen.seleccion.Seleccion;
import agen.util.exception.MetodoInexistente;
import agen.util.exception.PorcentajesInconsistentes;
import java.util.Random;

/**
 * Agrupa toda la funcionalidad relacoinada con los metodos de cruce.
 * @author juan
 */
public class Cruce {

    
    public static final int METODO_BINOMIAL = 0;
    public static final int METODO_PUNTO = 1;
    public static final int METODO_MULTI_PUNTO = 2;
    private static MetodoCruceCombinado[] metodoElegido =
    {new MetodoCruceCombinado(METODO_BINOMIAL, 100, null)};

    public static int cruce(int offset, int porcentaje, byte[] poblacionActual,
            byte[] poblacionModificada, double[] aptitudes,
            MetodoCruceCombinado[] metodoElegido, int eltosXIndividuo,
            int[] aptitudesOrdenadas)
            throws PorcentajesInconsistentes, MetodoInexistente {
        if (metodoElegido != null) {
            Cruce.metodoElegido = metodoElegido;
        }
        int sumPorcentaje = 0;
        for (MetodoCruceCombinado metComb : metodoElegido) {
            sumPorcentaje += metComb.porcentaje;
            if (!(metComb.metodo == METODO_BINOMIAL ||
                    metComb.metodo == METODO_MULTI_PUNTO ||
                    metComb.metodo == METODO_PUNTO)) {
                throw new MetodoInexistente("Seleccion.seleccion: el metodo en"
                        + " metodoElegido no está definido");
            }
        }
        if (!(sumPorcentaje == 100 || sumPorcentaje == 0)) {
            throw new PorcentajesInconsistentes("Seleccion.seleccion: los"
                    + " porcentajes en metodoElegido no suman 100%");
        }
        
        return aplicarMetodos(offset, porcentaje ,poblacionActual,
                poblacionModificada, aptitudes, eltosXIndividuo, aptitudesOrdenadas);
    }

    private static int aplicarMetodos(int offset, int porcentaje, byte[] poblacion,
            byte[] poblacionModificada, double[] aptitudes, int eltosXIndividuo,
            int[] aptitudesOrdenadas) {
        int len = 0;
		double porcentajeProporcional;
        for (int i = 0; i < metodoElegido.length; i++) {
            offset += len;
			porcentajeProporcional = (metodoElegido[i].porcentaje * porcentaje)
                    / 10000.0;
            len = (int) Math.round(porcentajeProporcional * aptitudes.length);
            /* si el redondeo hace que se pase el indice ajustamos en el ultimo
             * intrvalo*/
            if (offset + len > aptitudes.length) {
                len = aptitudes.length - offset;
            }
            switch (metodoElegido[i].metodo) {
                case METODO_BINOMIAL:
                    binomial(poblacion, poblacionModificada, offset, len, 
                            aptitudes, eltosXIndividuo, aptitudesOrdenadas);
                    break;
                case METODO_MULTI_PUNTO:
                    multiPunto(poblacion, poblacionModificada, offset, len, 
                            aptitudes, eltosXIndividuo, aptitudesOrdenadas,
                            metodoElegido[i].multipuntos);
                    break;
                case METODO_PUNTO:
                    punto(poblacion, poblacionModificada, offset, len, aptitudes, 
                            eltosXIndividuo, aptitudesOrdenadas,
                            metodoElegido[i].multipuntos);
                    break;
            }
        }
		return offset + len;
    }
/** ya no se usa, porque los operadores de seleccion, cruce y mutacion se concatenan
 para generar una sola poblacion modificada. por generacion. y cada uno devuelve el offset donde quedo.*/
    private static int saltearElitistas(byte[] poblacionActual,
            byte[] poblacionModificada, double[] aptitudes) {
        int offset = 0;
        int porcentajeElitista = 0;
        /*Si en la seleccion se uso elitista, saltear los individuos que deben
        pasar a la proxima genración sin modificarse.*/
        for (MetodoSeleccionCombinado metSelComb : Seleccion.getMetodoElegido()) {
            if (metSelComb.getMetodo() == Seleccion.METODO_ELITISTA) {
                /*es aconsejable que el primer metodo, si se usa, sea el elitista
                siempre*/
                porcentajeElitista = metSelComb.getPorcentaje();
                break;
            }
        }
        if (porcentajeElitista != 0) {
            offset = (int) Math.round((porcentajeElitista / 100.0) * aptitudes.length);
            for (int i = offset - 1; i < 0; i++) {
                poblacionModificada[i] = poblacionActual[i];
            }
            /*la suma de los porcentajes ahora pasa a ser 100 - porcentajeElitista*/
            for (MetodoCruceCombinado metSelComb : metodoElegido) {
                metSelComb.setPorcentaje((int) Math.round(
                        metSelComb.getPorcentaje() * (1 - (porcentajeElitista / 100.0))));
            }
        }
        return offset;
    }

    /**
     * En funcion de la aptitud del padre la probabilidead de pasar cada gen.
     * Por ahora, cada elto es equivalente  acada gen.
     * @param poblacion
     * @param aptitudes
     * @param eltosXIndividuo
     * @param aptitudesOrdenadas
     */
    private static void binomial(byte[] poblacion,  byte[] poblacionModificada,
            int offset, int len, double[] aptitudes, int eltosXIndividuo,
            int[] aptitudesOrdenadas) {
        Random r1 = new Random();
        int mama, papa, papaEnPoblacion, mamaEnPoblacion;
        int offsetEnPoblacion = offset * eltosXIndividuo;
        for (int i = (len - 1) * eltosXIndividuo; i >= 0; i -= eltosXIndividuo) {
            //TODO: CONTROLAR QUE CONINCIDA(len -1) * eltos.. comparar con implementacion en seleccion
            mama = r1.nextInt(aptitudes.length);
            papa = r1.nextInt(aptitudes.length);
            /* Al pasar por sort1 aptitudes se ordena de mayor a menor. En
             * aptitudesOrdenadas quedan las referencias a las ubicaciones
             * originales de cada aptitud, posision que las relaciona con los
             * individuos de la poblacion.
             */
            mamaEnPoblacion = aptitudesOrdenadas[mama] * eltosXIndividuo;
            papaEnPoblacion = aptitudesOrdenadas[papa] * eltosXIndividuo;
            /*rand entre 0 y (ap[mama] + ap[papa]) > ap[mama] => elto[papa]*/
            /**3 pasos para hacer que las aptitudes sirvan para la asignacion
            de probabilidades a la mamá y el papá
            0) -x <---+------------|---------|----------|-----------|--> +x
             *         0           Max       mama       papa        Min
            1) Ap - Min:
             * -x <--------|---------|----------|-----------+------> +x
             *            Max       mama       papa     Min = 0
            2) (1) * -1 = (Ap - Min) * -1
             *             |------------------->mama
             *             |-------->papa
             * -x <--------+---------|----------|-----------|------> +x
             *         Min = 0      papa       mama        Max
             * Con papa y mama: (-Ap[mama] + Min)(-Ap[papa] + Min) =
             * = (2 * Min - Ap[papa] - Ap[mama])
            3) Random(papa + mama):
             *
             *             |-----mama---------->--papa-->|
             * -x <--------+-----------------------------|--------> +x
             *             0------------random---------->|
             */
            double minAp = aptitudes[aptitudes.length - 1]; //La aptitud mayor es la peor
            double topeRand = 2 * minAp - aptitudes[mama] - aptitudes[papa];
            double topeMama = minAp - aptitudes[mama];
            double rand;
            for (int j = eltosXIndividuo - 1; j >= 0; j--) {
                rand = r1.nextDouble() * topeRand;
                poblacionModificada[offsetEnPoblacion + i + j] = rand >= topeMama
                        ? poblacion[papaEnPoblacion + j] : poblacion[mamaEnPoblacion + j];
            }
        }

    }

    private static void punto(byte[] poblacion,  byte[] poblacionModificada, 
            int offset, int len, double[] aptitudes, int eltosXIndividuo,
            int[] aptitudesOrdenadas, int[] multipunto) {
        Random r1 = new Random();
        int mama, papa, papaEnPoblacion, mamaEnPoblacion, punto;
        int offsetEnPoblacion = offset * eltosXIndividuo;
        punto = (multipunto == null || multipunto.length == 0) ? r1.nextInt(
                eltosXIndividuo - 1) : multipunto[0];
        for (int i = (len - 1) * eltosXIndividuo; i >= 0
                ;i -= 2 * eltosXIndividuo) {
            mama = r1.nextInt(aptitudes.length);
            papa = r1.nextInt(aptitudes.length);
            mamaEnPoblacion = aptitudesOrdenadas[mama] * eltosXIndividuo;
            papaEnPoblacion = aptitudesOrdenadas[papa] * eltosXIndividuo;
            for (int j = eltosXIndividuo - 1; j >= 0; j--) {
                if (j > punto) {
                    poblacionModificada[offsetEnPoblacion + i + j] =
                            poblacion[mamaEnPoblacion];
                    if (i >= eltosXIndividuo) {
                        /*aca hacemos el hermano complemento*/
                        poblacionModificada[
                                offsetEnPoblacion + i - eltosXIndividuo + j]
                                = poblacion[papaEnPoblacion];
                    }
                } else {
                    poblacionModificada[offsetEnPoblacion + i + j] =
                            poblacion[papaEnPoblacion];
                    if (i >= eltosXIndividuo) {
                        /*aca hacemos el hermano complemento*/
                        poblacionModificada[
                                offsetEnPoblacion + i - eltosXIndividuo + j]
                                = poblacion[mamaEnPoblacion];
                    }
                }
            }
        }
    }

    private static void multiPunto(byte[] poblacion,  byte[] poblacionModificada, 
            int offset, int len, double[] aptitudes, int eltosXIndividuo,
            int[] aptitudesOrdenadas, int[] multipunto)
            throws IllegalArgumentException {
        Random r1 = new Random();
        int mama, papa, papaEnPoblacion, mamaEnPoblacion, base, tope;
        boolean mamaArriba = true;
        if (multipunto == null || multipunto.length == 0) {
            throw new IllegalArgumentException("Cruce.multiPunto: No se " +
                    "especificó in array int[] multipunto válido.");
        }
        for (int i = (len - 1) * eltosXIndividuo; i >= 0; i -= 2 * eltosXIndividuo) {
            mama = r1.nextInt(aptitudes.length);
            papa = r1.nextInt(aptitudes.length);
            mamaEnPoblacion = aptitudesOrdenadas[mama] * eltosXIndividuo;
            papaEnPoblacion = aptitudesOrdenadas[papa] * eltosXIndividuo;
            base = 0;
            /*TODO: esta es la parte mas ineficiente, ver que se puede con la
             llamada aparearMultiPunto*/
            for (int k = multipunto.length - 1; k >= 0; k--) {
                tope = multipunto[k];//inclusive
                aparearMultiPunto(tope, base, poblacion, poblacionModificada, mamaEnPoblacion,
                        papaEnPoblacion, offset, i, mamaArriba, eltosXIndividuo);
                base = tope + 1;
                mamaArriba = !mamaArriba;
            }
            if (multipunto[multipunto.length - 1] != eltosXIndividuo - 1) {
                /*queda implicito el ultimointervalo*/
                aparearMultiPunto(eltosXIndividuo - 1, base, poblacion,poblacionModificada,
                        mamaEnPoblacion, papaEnPoblacion, offset, i, mamaArriba,
                        eltosXIndividuo);
            }

        }
    }

    private static void aparearMultiPunto(int tope, int base, byte[] poblacion, 
            byte[] poblacionModificada, int mamaEnPoblacion, int papaEnPoblacion,
            int offset, int desplazamiento, boolean mamaArriba, int eltosXIndividuo) {
        int offsetEnPoblacion = offset * eltosXIndividuo;
        for (int j = tope; j >= base; j--) {
            if (mamaArriba) {
                poblacionModificada[offsetEnPoblacion + desplazamiento + j]
                        = poblacion[mamaEnPoblacion];
                if (desplazamiento >= eltosXIndividuo) {
                    /*aca hacemos el hermano complemento*/
                    poblacionModificada[offsetEnPoblacion + desplazamiento - eltosXIndividuo + j] 
                            = poblacion[papaEnPoblacion];
                }
            } else {
                poblacionModificada[offsetEnPoblacion + desplazamiento + j]
                        = poblacion[papaEnPoblacion];
                if (desplazamiento >= eltosXIndividuo) {
                    /*aca hacemos el hermano complemento*/
                    poblacionModificada[
                            offsetEnPoblacion + desplazamiento - eltosXIndividuo + j]
                            = poblacion[mamaEnPoblacion];
                }
            }
        }
    }
}

